package com.keruyun.talent.basic.component.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * redis 分布式锁
 * 
 * @author admin
 * @version [版本号, 2017年8月15日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public abstract class RedisLock {

	/**
	 * 获取锁时间间隔
	 */
	private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;

	/**
	 * 锁有效期，防止死锁
	 */
	private int expireMsecs = 60 * 1000;

	/**
	 * 获取锁重试测试
	 * 
	 */
	private int retryTimes = 1;

	private String lockKey;

	private volatile boolean locked = false;

	protected Logger logger = LoggerFactory.getLogger(getClass());

	public RedisLock(String lockKey) {
		this.lockKey = lockKey + "_lock";
	}

	public RedisLock(String lockKey, int retryTimes) {
		this(lockKey);
		this.retryTimes = retryTimes;
	}

	public RedisLock(String lockKey, int retryTimes, int expireMsecs) {
		this(lockKey, retryTimes);
		this.expireMsecs = expireMsecs;
	}

	/**
	 * 获取redis锁
	 *
	 * @param key
	 * @return [参数说明]
	 * @return boolean [返回类型说明]
	 * @see [类、类#方法、类#成员]
	 */
	public synchronized boolean lock() {
		int timeout = retryTimes;
		while (timeout > 0) {
			Long value = generateValue();
			if (tryAcquireLock(value)) {
				locked = true;
				return true;
			}
			if (retryGetLock(value)) {
				locked = true;
				return true;
			}
			// 重试次数
			timeout--;
			if (timeout > 0) {
				try {
					Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
				} catch (InterruptedException e) {
					return false;
				}
			}
		}
		return false;
	}

	/**
	 * 释放锁
	 * 
	 * 
	 * @see [类、类#方法、类#成员]
	 */
	public void unLock() {
		if (locked) {
			logger.info("删除redis key:{}", getLockKey());
			// deleteKey();
		}
	}

	protected String getLockKey() {
		return lockKey;
	}

	protected abstract void deleteKey();

	protected abstract Long get();

	protected abstract Boolean setNx(Long value);

	protected abstract Long getAndSet(Long value);

	/**
	 * 尝试获取分布式锁
	 *
	 * @param key
	 * @return [参数说明]
	 * @return boolean [返回类型说明]
	 * @see [类、类#方法、类#成员]
	 */
	private boolean tryAcquireLock(Long value) {
		return setNx(value);
	}

	/**
	 * 尝试重新获取锁
	 *
	 * @param value
	 * @return [参数说明]
	 * @return boolean [返回类型说明]
	 * @see [类、类#方法、类#成员]
	 */
	private boolean retryGetLock(Long value) {
		// 未获取到锁，检测锁是否超时
		Long currentValueLong = get();
		// 当前时间 > 锁设置时间
		if (currentValueLong == null) {
			return false;
		}
		// redis中时间小于当前时间 情况1.redis值过期，2.其他线程修改了redis的值，有效期内多线程修改redis值是可以接收的
		boolean expired = System.currentTimeMillis() > currentValueLong;
		// 尝试设置值,返回旧的值，防止在get瞬间有其他未client修改redis值
		if (expired) {
			Long oldValueLong = getAndSet(value);
			// 如果值未改变，说明没有其他客户端获取锁修改值
			if (oldValueLong != null && oldValueLong.equals(currentValueLong)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 生成值
	 *
	 * @return [参数说明]
	 * @return String [返回类型说明]
	 * @see [类、类#方法、类#成员]
	 */
	private Long generateValue() {
		// 当前时间+锁超时时间+1 ,+1是为防止代码执行时间误差
		long currentTimeMillis = System.currentTimeMillis();
		long expires = currentTimeMillis + expireMsecs + 1;
		return expires;
	}

	public boolean isLocked() {
		return locked;
	}
}
